Skip to content

cmake: linker: Use the same linker for cmake checks and final build #77666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

jonathonpenix
Copy link
Contributor

@jonathonpenix jonathonpenix commented Aug 28, 2024

Currently, the linker that is used when performing various cmake checks (check_c_compiler_flag, for example) may be different than the linker that will be used during the actual build. This happens as we currently specify '-fuse-ld' to force the appropriate linker a) after many such checks have already happened and b) in a way which is not automatically propagated to check_c_compiler_flag (and friends). As a result, the toolchain's default linker will generally be used for such checks regardless of which linker was selected in Zephyr.

This can lead to a number of surprises when building Zephyr, particularly when building with clang. For example:

  • If the linker is misconfigured, where the build will fail can vary depending on whether the linker is the toolchain's default. When the configured linker happens to be the toolchain's default, the build (helpfully) fails quickly on the checks for a basic working toochain. When the configured linker isn't the default, the build won't fail until the final link steps.
  • The build can fail due to issues with a linker other than the one configured by the user in Zephyr. For example, LLVM toolchains without lld will generally fail to build Zephyr (the checks for a basic working toochain will fail) for targets where lld is the default in LLVM even if GNU ld is configured in Zephyr and would otherwise be used in the final build.
  • Flags which are only added if check_c_compiler_flag (or similar) succeeds may be unexpectedly omitted during the final build if the flag is supported in the configured linker but is unsupported in the toolchain's default linker (as check_c_compiler_flag will test using the default one).

Note that this isn't limited to clang--even when we are building with Zephyr's SDK and force ld.bfd, we seem to use the 'ld' variant during the cmake checks (though this generally seems fairly harmless compared to mixing ld/lld or other proprietary linkers).

To fix this, ensure the appropriate 'fuse-ld' is set early enough and in such a way that the same linker will be used throughout the entire build.

Currently, the linker that is used when performing various cmake checks
(check_c_compiler_flag, for example) may be different than the linker that
will be used during the actual build. This happens as we currently specify
'-fuse-ld' to force the appropriate linker a) after many such checks have
already happened and b) in a way which is not automatically propagated to
check_c_compiler_flag (and friends). As a result, the toolchain's default
linker will generally be used for such checks regardless of which linker
was selected in Zephyr.

This can lead to a number of surprises when building Zephyr, particularly
when building with clang. For example:

- If the linker is misconfigured, where the build will fail can vary
  depending on whether the linker is the toolchain's default. When the
  configured linker happens to be the toolchain's default, the build
  (helpfully) fails quickly on the checks for a basic working toochain.
  When the configured linker isn't the default, the build won't fail until
  the final link steps.
- The build can fail due to issues with a linker other than the one
  configured by the user in Zephyr. For example, LLVM toolchains without
  lld will generally fail to build Zephyr (the checks for a basic
  working toochain will fail) for targets where lld is the default in LLVM
  even if GNU ld is configured in Zephyr and would otherwise be used in the
  final build.
- Flags which are only added if check_c_compiler_flag (or similar) succeeds
  may be unexpectedly omitted during the final build if the flag is
  supported in the configured linker but is unsupported in the toolchain's
  default linker (as check_c_compiler_flag will test using the default
  one).

Note that this isn't limited to clang--even when we are building with
Zephyr's SDK and force ld.bfd, we seem to use the 'ld' variant during the
cmake checks (though this generally seems fairly harmless compared to
mixing ld/lld or other proprietary linkers).

To fix this, ensure the appropriate 'fuse-ld' is set early enough and in
such a way that the same linker will be used throughout the entire build.

Signed-off-by: Jonathon Penix <[email protected]>
Copy link
Contributor Author

@jonathonpenix jonathonpenix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ready for review, but there are a few places where I'm still a bit unsure if I will break things for other users (please see the inline comments below). Any advice/suggestions here would be much appreciated!

@@ -6,6 +6,10 @@ set(CMAKE_LINKER ${LLVMLLD_LINKER})

set_ifndef(LINKERFLAGPREFIX -Wl)

list(APPEND TOOLCHAIN_LD_FLAGS -fuse-ld=lld)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two thoughts here:

  1. Conceptually, setting -fuse-ld here rather than target_baremetal.cmake makes sense to me as I don't think -fuse-ld should be considered a baremetal-specific flag. I don't think this should cause issues for other toolchains (if they relied on it not being specified for non-baremetal cases for some reason, for example) but I'm not sure if there is something I'm missing. oneAPI, for example, seems to use lld but -fuse-ld=lld should work as expected as best I can tell from the docs.
  2. I dropped the if(NOT CONFIG_LLVM_USE_LD) check from the old location. This is an unrelated change I guess, but since I was moving this code it didn't make sense to me to keep it--I don't think we should be in lld/target.cmake unless a) CONFIG_LLVM_USE_LLD was selected or b) we're using a non-LLVM toolchain at which point I don't think CONFIG_LLVM_USE_LD should be set, so the check is always true. I can readd the if if desired/if I'm misunderstanding though!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually, setting -fuse-ld here rather than target_baremetal.cmake makes sense to me as I don't think -fuse-ld should be considered a baremetal-specific flag.

agree. unfortunately the existing toolchain_ld_base and toolchain_ld_baremetal macros has become a bit messy over time and thus diverted from their initial purpose.

A first approach in cleaning up this without introducing functional changes is done here #77887.
Although that cleanup doesn't target the mis-placement of -fuse-ld=lld, then it might have your interest.

I dropped the if(NOT CONFIG_LLVM_USE_LD) check from the old location. ...
I don't think we should be in lld/target.cmake unless a) CONFIG_LLVM_USE_LLD was selected

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree. unfortunately the existing toolchain_ld_base and toolchain_ld_baremetal macros has become a bit messy over time and thus diverted from their initial purpose.

A first approach in cleaning up this without introducing functional changes is done here #77887. Although that cleanup doesn't target the mis-placement of -fuse-ld=lld, then it might have your interest.

Thank you for the pointer (and for the patch itself too)! That makes sense to me--I'll fix this up accordingly soon now that that one has landed!

if((${CMAKE_LINKER} STREQUAL "${CROSS_COMPILE}ld.bfd") OR
${GNULD_LINKER_IS_BFD})
# ld.bfd was found so let's explicitly use that for linking, see #32237
list(APPEND TOOLCHAIN_LD_FLAGS -fuse-ld=bfd)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think moving this here is valid but I'm not entirely sure. IIUC, previously -fuse-ld=bfd would only be added when toolchain_ld_link_elf is called for a given target (and it's dependencies). By adding this to TOOLCHAIN_LD_FLAGS instead (which will then be added via zephyr_ld_options when toolchain_ld_base is invoked), I think we'll effectively add -fuse-ld=bfd everywhere we link. As toolchain_ld_link_elf is (IIUC) invoked for each of the link stages (pre0/1, final) it seems like this change should be equivalent--are there use cases where we're both linking other things and truly don't want to use the same linker variant as the "main" .elfs?

I don't see anything concerning in 39f06e0 where this was first added, but I'm not sure if I'm missing/misunderstanding something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a fine location.

@@ -6,6 +6,10 @@ set(CMAKE_LINKER ${LLVMLLD_LINKER})

set_ifndef(LINKERFLAGPREFIX -Wl)

list(APPEND TOOLCHAIN_LD_FLAGS -fuse-ld=lld)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually, setting -fuse-ld here rather than target_baremetal.cmake makes sense to me as I don't think -fuse-ld should be considered a baremetal-specific flag.

agree. unfortunately the existing toolchain_ld_base and toolchain_ld_baremetal macros has become a bit messy over time and thus diverted from their initial purpose.

A first approach in cleaning up this without introducing functional changes is done here #77887.
Although that cleanup doesn't target the mis-placement of -fuse-ld=lld, then it might have your interest.

I dropped the if(NOT CONFIG_LLVM_USE_LD) check from the old location. ...
I don't think we should be in lld/target.cmake unless a) CONFIG_LLVM_USE_LLD was selected

👍

if((${CMAKE_LINKER} STREQUAL "${CROSS_COMPILE}ld.bfd") OR
${GNULD_LINKER_IS_BFD})
# ld.bfd was found so let's explicitly use that for linking, see #32237
list(APPEND TOOLCHAIN_LD_FLAGS -fuse-ld=bfd)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a fine location.

@tejlmand
Copy link
Contributor

@jonathonpenix I've taken this cleanup PR, adjusted it a bit to adopt to the changes from #77887 and included the changes in #78320 as 789b9df

@jonathonpenix
Copy link
Contributor Author

@jonathonpenix I've taken this cleanup PR, adjusted it a bit to adopt to the changes from #77887 and included the changes in #78320 as 789b9df

Sounds good, thank you! I'll close this PR for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants